﻿using MudEngine.Collections;
using MudEngine.Parsing;
using MudEngine.Runtime;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Remoting;
using System.Threading;

namespace MudEngine
{
	public class ObjectManager
	{
		/// <summary>
		/// 
		/// </summary>
		private const string SOURCE_EXT = ".c";
		/// <summary>
		/// 
		/// </summary>
		private object _lock = new object();

		private static int _cloneId;

        public Interpreter Interpreter { get; set; }
        public Compiler Compiler { get; set; }

		/// <summary>
		/// 
		/// </summary>
		public ObjectManager()
		{
			//this.StartCallOutThread();

			// Load auto object here?
		}
        /// <summary>
        /// 
        /// </summary>
        private WeakReferenceList _objects;
        /// <summary>
        /// 
        /// </summary>
        public WeakReferenceList All
        {
            get
            {
                if (this._objects == null)
                    this._objects = new WeakReferenceList(100, 30);

				return this._objects;
            }
        }
		/// <summary>
		/// 
		/// </summary>
		/// <param name="name"></param>
		/// <returns></returns>
		public object_t FindByName(string name)
		{
			object_t obj = this.FindObject(name);

			if (obj != null)
			{
				obj.Touch();
				return obj;
			}

			obj = LoadObject(name);

			return obj;
		}
        /// <summary>
        /// Find loaded object. If not in cache, return null
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public object_t FindObject(string name)
        {
			return this.All[name];

        }
		/// <summary>
		/// Loads and compiles an object from disk.
		/// </summary>
		/// <param name="name"></param>
		/// <returns></returns>
		public object_t LoadObject(string name)
		{
            object_t ob = null;

			string correctedString = CheckName(name);
			FileInfo fi = GetSourceFile(correctedString);
            if (!fi.Exists)
                Interpreter.Error("File '{0}' could not be found", name);

			Program prog = Compiler.Compile(correctedString + SOURCE_EXT, fi);

			if (prog != null)
			{
                // Inherited object not yet loaded. (see avecos.atg -> InheritDirective)
                if (prog.Inherits.Count > 0 && prog.Inherits[prog.Inherits.Count - 1].Program == null)
                {
                    Inherit inherit = prog.Inherits[prog.Inherits.Count - 1];

                    if (name.Equals(inherit.Filename))
                        Interpreter.Error("{0} cannot inherit itself", name);

                    // load inherited program
                    object_t inhObj = this.FindObject(inherit.Filename);
                    if (inhObj != null)
                        Interpreter.Error("Inherited object '{0}' has already been loaded.", inherit.Filename);
                    inhObj = this.LoadObject(inherit.Filename);
                    if (inhObj == null)
                        Interpreter.Error("Object '{0}' could not be found", inherit.Filename);

                    // inherited program loaded. Reload intended object
                    ob = this.FindObject(name);
                    if (ob == null)
                        ob = this.LoadObject(name);

                    return ob;
                }

				ob = new object_t(name, Interpreter);
                ob.Program = prog;
                ob.Create();
                //prog.Decode();
                this.All[ob.Name] = ob;

                return ob;
			}

			return null;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="name"></param>
		/// <returns></returns>
		public object_t CloneObject(string name)
		{
			object_t obj = this.FindByName(name);
			if (obj == null)
				return null;

			if (obj.Flags.HasFlag(ObjectFlags.O_MASTER))
                Interpreter.Error("Cannot clone the master object");

			if (obj.Flags.HasFlag(ObjectFlags.O_CLONE))
                Interpreter.Error("Cannot clone a clone");

			object_t newObject = new object_t(string.Format("{0}#{1}", name, ++_cloneId), Interpreter);
			newObject.Flags |= ObjectFlags.O_CLONE;
			newObject.Program = obj.Program;
			newObject.Create();

            this.All[newObject.Name] = newObject;

			return newObject;

		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="objectName"></param>
		/// <returns></returns>
		private static string CheckName(string objectName)
		{
			if (!objectName.StartsWith("/") && !objectName.StartsWith(Path.DirectorySeparatorChar.ToString()))
				objectName = "/" + objectName;

			return objectName;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="objectName"></param>
		/// <returns></returns>
		private static FileInfo GetSourceFile(string objectName)
		{
			string sourceDir = MudContext.LIB_DIR;

			return new FileInfo(sourceDir + objectName.Replace('/', Path.DirectorySeparatorChar) + SOURCE_EXT);
		}

        private const long MAX_TASKS_TICKS = TimeSpan.TicksPerMillisecond * 200;

        public void HandleTasks()
        {
            // handle heartbeat
            // handle callouts
            // cleanup objects
        }

        public void CleanUp()
        {
            long _s = DateTime.Now.Ticks;
            DateTime checkDate = DateTime.Now.AddMinutes(-1);

            this.All.Clean(5000);
            /*
            foreach (string key in this.All)
            {
                if (DateTime.Now.Ticks - _s < 5000)
                {
                    if (!this.All.ContainsKey(key))
                    {
                        this.All.RemoveKey(key);
                        Console.WriteLine("Clean: " + key);
                    }
                }
            }
            */
        }












        /*
        /// <summary>
		/// 
		/// </summary>
		/// <param name="obj"></param>
        public void Destruct(ContainerItem item)
        {
            if (item == null)
                throw new ArgumentNullException();
            if (item.Flags.HasFlag(ObjectFlags.O_DESTRUCTED))
                return;

            Log.Debug(this.GetType(), string.Format("[{0}] destruct item", item));
            if (!item.Flags.HasFlag(ObjectFlags.O_DESTRUCTED))
                item.Flags |= ObjectFlags.O_DESTRUCTED;

            item.Call("OnDestruct");
            //this.Unregister((MarshalByRefObject)item.Data);
            item.Data = null;

            if (item.IsClone)
                item.Master.RefCount--;

            //UniqueItems.Remove(item.Id);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        public void Destruct(IObject obj)
        {
            try
            {
				ContainerItem item = null; // FindItem(obj.RefID);
                if (item != null)
                    this.Destruct(item);
            }
            catch (Exception e)
            {
                Log.Debug(this.GetType(), "Unable to destruct object", e);
            }
        }
        */

        /*
		#region CallOut Members
		private PriorityQueue<CallOut, DateTime> _callOuts = new PriorityQueue<CallOut, DateTime>();
		/// <summary>
		/// 
		/// </summary>
		public void StartCallOutThread()
		{
			Thread newthread = new Thread(new ThreadStart(CallOutManager));
			newthread.Start();
		}
		/// <summary>
		/// 
		/// </summary>
		private void CallOutManager()
		{
			PriorityQueueItem<CallOut, DateTime> pqi = null;

			CallOut co = null;
			IObject obj = null;
			bool handleCallOut = false;

			while (true)
			{
				handleCallOut = false;
				if (_callOuts.Count > 0)
				{
					lock (_lock)
					{
						if (_callOuts.Peek().Priority <= DateTime.Now)
						{
							pqi = _callOuts.Dequeue();
							handleCallOut = true;
						}
					}
					if (handleCallOut)
					{
                        try
                        {
                            co = pqi.Value;

                            obj = co.Target;
                            obj.Call(co.Function, co.Arguments);
                        }
                        catch (RemotingException re)
                        {
                            // object has left the building
                            // ignore it because it is already handled by the ContainerManager
                            Log.Debug(this.GetType(), "Object has already been destroyed. Ignore error.", re);
                        }
                        catch (Exception e)
                        {
                            Log.Debug(this.GetType(), "Error during CallOut", e);
                        }
					}
					else
					{
						Thread.Sleep(500);
					}
				}
				else
				{
					Thread.Sleep(500);
				}
			}
		}
        
		#endregion
         */

		/*
        #region System Calls
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="function"></param>
        /// <param name="delay"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public CallOut __system_AddCallOut(IObject obj, string function, int delay, params object[] args)
        {
            CallOut co = new CallOut(obj, function, delay, args);
            lock (_callOuts)
            {
                _callOuts.Enqueue(co, co.ExecuteAfter);
            }
            return co;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="callOut"></param>
        public void __system_RemoveCallOut(CallOut callOut)
        {
            lock (this._callOuts)
            {
                this._callOuts.Remove(callOut);
            }
        }
        /// <summary>
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="reason"></param>
        public void __system_Disconnect(IObject obj, string reason)
        {
            IContainerItem item = this.FindItem(obj.RefID);

            if (!item.Flags.HasFlag(ObjectFlags.O_INTERACTIVE))
                throw new Exception("Object is not an USER");

            if (CommunicationManager.Instance.Disconnect(item))
                this.Destruct(item);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="func"></param>
        public void __system_InputTo(string func)
        {
            if (this.CommandGiver.TcpConnection != null && !this.CommandGiver.TcpConnection.HasInputTo())
                this.CommandGiver.TcpConnection.InputTo = func;
        }
        #endregion
		*/
    }
    /*
	[Serializable]
	public class CallOut
	{
		public IObject Target { get; private set; }
		public string Function { get; private set; }
		public int Delay { get; private set; }
		public DateTime ExecuteAfter { get; private set; }
		public object[] Arguments { get; private set; }

		public CallOut(IObject target, string function, int delay, params object[] args)
		{
			this.Target = target;
			this.Function = function;
			this.Delay = delay;
			this.Arguments = args;

			this.ExecuteAfter = DateTime.Now.AddSeconds(delay);
		}
		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public override int GetHashCode()
		{
			return Target.GetHashCode() + (int)ExecuteAfter.Ticks;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="obj"></param>
		/// <returns></returns>
		public override bool Equals(object obj)
		{
			if (obj == null)
				return false;

			CallOut otherObj = obj as CallOut;
			if (otherObj == null)
				return false;

			return this.GetHashCode() == otherObj.GetHashCode();
		}
	}
     * */
}
